﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Noris.Tools.TraceVisualiser.Forms;

namespace Noris.Tools.TraceVisualiser.TraceData
{
    #region class DataMatrix
    /// <summary>
    /// Třída pro shrnování dat řádků a jejich sloupců z různých zdrojů, mergování sloupců do jedné tabulky.
    /// </summary>
    public class DataMatrix : IDisposable
    {
        #region Konstrukce
        public DataMatrix()
        {
            this._Rows = new List<MatrixRow>();
            this._Columns = new Dictionary<string, MatrixColumn>();
        }
        private List<MatrixRow> _Rows;
        private Dictionary<string, MatrixColumn> _Columns;
        void IDisposable.Dispose()
        {
            this._Rows = null;
            this._Columns = null;
            this._SBuild = null;
        }
        #endregion
        #region protected classes MatrixRow, MatrixColumn
        /// <summary>
        /// Data o jednom řádku
        /// </summary>
        protected class MatrixRow
        {
            public MatrixRow()
            {
                this.Cells = new Dictionary<string, object>();
            }
            /// <summary>
            /// Buňky. Klíčem je ColumnKey, obsahem Value.
            /// </summary>
            public Dictionary<string, object> Cells { get; private set; }
        }
        /// <summary>
        /// Data o jednom sloupci
        /// </summary>
        protected class MatrixColumn
        {
            public MatrixColumn(string columnKey)
            {
                this.ColumnKey = columnKey;
            }
            /// <summary>
            /// ID sloupce, klíčový název.
            /// Buňky řádků, které budou mít shodný ColumnKey, budou vypsány pod sebou.
            /// Pokud jeden řádek bude mít více buněk se shodným ColumnKey, dojde k chybě.
            /// ColumnKey nesmí být null ani empty, dojde k chybě.
            /// </summary>
            public string ColumnKey { get; private set; }
            /// <summary>
            /// Titulek sloupce.
            /// Pokud bude pro jeden ColumnKey více různých ColumnCaption, pak se zobrazí nejdelší z nich.
            /// </summary>
            public string ColumnCaption { get; set; }
            /// <summary>
            /// Pořadí sloupce.
            /// Pokud budou dva sloupce se shodným pořadím, budou tříděny podle ColumnKey.
            /// Pokud bude pro jeden ColumnKey více různých hodnot ColumnOrder, pak se použije ta nejnižší z nich, větší než 0.
            /// </summary>
            public int ColumnOrder { get; set; }
            /// <summary>
            /// Je to poslední sloupec vpravo?
            /// </summary>
            public bool IsLast { get; set; }
            /// <summary>
            /// Komparátor pro třídění Listu podle ColumnOrder ASC, ColumnKey ASC
            /// </summary>
            /// <param name="a"></param>
            /// <param name="b"></param>
            /// <returns></returns>
            internal static int CompareForSort(MatrixColumn a, MatrixColumn b)
            {
                if (a == null && b == null) return 0;
                if (a == null) return -1;
                if (b == null) return 1;

                int cmp = a.ColumnOrder.CompareTo(b.ColumnOrder);
                if (cmp != 0) return cmp;

                return String.Compare(a.ColumnKey, b.ColumnKey);
            }
        }
        #endregion
        #region Clear. Add rows
        /// <summary>
        /// Vymaže obsah objektu
        /// </summary>
        public void Clear()
        {
            this._Rows = new List<MatrixRow>();
            this._Columns = new Dictionary<string, MatrixColumn>();
        }
        /// <summary>
        /// Přidá další řádek, pocházející z IMasterDetails
        /// </summary>
        /// <param name="iDet"></param>
        public void AddRow(IMasterDetails iDet)
        {
            MatrixRow row = new MatrixRow();
            DataMatrixCell iCell;

            iCell = new DataMatrixCell() { ColumnKey = "__MASTER_DETAIL_TITLE__", ColumnCaption = "Info", ColumnOrder = 1, CellValue = iDet.MasterName };
            this._AddICell(iCell, row, iDet);

            foreach (MasterDetailItem item in iDet.Details)
            {
                if (!String.IsNullOrEmpty(item.Name) && item.ShowInGrid)
                {
                    iCell = new DataMatrixCell() { ColumnKey = item.Name, ColumnCaption = item.Name, CellValue = item.Value };
                    this._AddICell(iCell, row, iDet);
                }
            }

            this._Rows.Add(row);
        }
        /// <summary>
        /// Přidá další řádek, pocházející z IDataMatrixRow
        /// </summary>
        /// <param name="iRow"></param>
        public void AddRow(IDataMatrixRow iRow)
        {
            MatrixRow row = new MatrixRow();
            
            foreach (IDataMatrixCell iCell in iRow.Cells)
                this._AddICell(iCell, row, iRow);

            this._Rows.Add(row);
        }
        /// <summary>
        /// Zajistí přidání sloupce podle definice buňky iCell, přidá hodnotu z buňky do řádku row.
        /// </summary>
        /// <param name="iCell"></param>
        /// <param name="row"></param>
        private void _AddICell(IDataMatrixCell iCell, MatrixRow row, object source)
        {
            if (iCell.ColumnKey == null)
            {
                string sourceText = (source == null ? "null" : source.GetType().Name + "; " + source.ToString());
                throw new System.ArgumentException("Invalid key: null value. Source object: " + sourceText);
            }
            if (row.Cells.ContainsKey(iCell.ColumnKey))
            {
                string sourceText = (source == null ? "null" : source.GetType().Name + "; " + source.ToString());
                object currValue = row.Cells[iCell.ColumnKey];
                string currString = (currValue == null ? "null" : currValue.ToString());
                string newString = (iCell.CellValue == null ? "null" : iCell.CellValue.ToString());
                throw new System.ArgumentException("Duplicate key " + iCell.ColumnKey.ToString() + " in MatrixRow. CurrentValue=" + currString + "; NewValue=" + newString + ". Source object: " + sourceText);
            }
            this._AddColumn(iCell);
            row.Cells.Add(iCell.ColumnKey, iCell.CellValue);
        }
        /// <summary>
        /// Zajistí existenci a korektní obsah sloupce
        /// </summary>
        /// <param name="iCell"></param>
        private void _AddColumn(IDataMatrixCell iCell)
        {
            string key = iCell.ColumnKey;
            MatrixColumn column;
            if (!this._Columns.TryGetValue(key, out column))
            {
                column = new MatrixColumn(key);
                this._Columns.Add(key, column);
            }
            
            // Mergování vlastností columnů:
            if (!String.IsNullOrEmpty(iCell.ColumnCaption) && (String.IsNullOrEmpty(column.ColumnCaption) || column.ColumnCaption.Length < iCell.ColumnCaption.Length))
                column.ColumnCaption = iCell.ColumnCaption;

            if (iCell.ColumnOrder > 0 && (column.ColumnOrder <= 0 || iCell.ColumnOrder < column.ColumnOrder))
                column.ColumnOrder = iCell.ColumnOrder;
            if (column.ColumnOrder <= 0)
                column.ColumnOrder = this._Columns.Values.Max(mc => mc.ColumnOrder) + 1;
        }
        #endregion
        #region Get data
        /// <summary>
        /// Příznak, že matrix obsahuje data
        /// </summary>
        public bool ContainData { get { return (this._Rows.Count > 0 && this._Columns.Count > 0); } }
        /// <summary>
        /// Vrátí textovou tabulku vytvořenou z aktuálních dat
        /// </summary>
        /// <returns></returns>
        public string GetTable()
        {
            this._SBuild = new StringBuilder();

            List<MatrixColumn> columns = this.ColumnsSorted;
            string endColumn = "\t";
            string endLine = Environment.NewLine;

            // Title:
            foreach (MatrixColumn column in columns)
            {
                this._SBuild.Append(column.ColumnCaption + (column.IsLast ? endLine : endColumn));
            }

            // Rows:
            foreach (MatrixRow row in this._Rows)
            {
                foreach (MatrixColumn column in columns)
                {   // Column in row:
                    string value = this.GetValue(row, column);
                    this._SBuild.Append(value + (column.IsLast ? endLine : endColumn));
                }
            }

            return this._SBuild.ToString();
        }
        /// <summary>
        /// Vrátí text z daného řádku z daného sloupce.
        /// </summary>
        /// <param name="row"></param>
        /// <param name="column"></param>
        /// <returns></returns>
        private string GetValue(MatrixRow row, MatrixColumn column)
        {
            object value;
            if (!row.Cells.TryGetValue(column.ColumnKey, out value)) return "";

            if (value == null) return "";

            // Konverze value (object) na text, včetně formátování:
            return this.ConvertValue(value);
        }
        /// <summary>
        /// Vrátí text z dané hodnoty
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        private string ConvertValue(object value)
        {
            if (value is string)
            {
                string text = value as string;
                text = text.Replace('\r', ' ').Replace('\n', ' ').Replace('\t', ' ');
                return text;
            }
            return value.ToString();
        }
        private StringBuilder _SBuild;
        /// <summary>
        /// Setříděný seznam sloupců, tříděný podle potřebného pořadí
        /// </summary>
        protected List<MatrixColumn> ColumnsSorted
        {
            get
            {
                List<MatrixColumn> list = new List<MatrixColumn>(this._Columns.Values);
                list.Sort(MatrixColumn.CompareForSort);
                list.ForEach(c => c.IsLast = false);
                if (list.Count > 0)
                    list[list.Count - 1].IsLast = true;
                return list;
            }
        }
        #endregion
    }
    #endregion
    #region classes DataMatrixRow a DataMatrixCell: Defaultní implementace IDataMatrixRow a IDataMatrixCell
    /// <summary>
    /// Defaultní implementace IDataMatrixRow
    /// </summary>
    internal class DataMatrixRow : IDataMatrixRow
    {
        public DataMatrixRow()
        {
            this.Cells = new List<DataMatrixCell>();
        }
        /// <summary>
        /// Seznam buněk v tomto řádku.
        /// </summary>
        public List<DataMatrixCell> Cells { get; private set; }
        /// <summary>
        /// Explicitní implementace IDataMatrixRow.
        /// Proč? Kvůli typu (interface má IEnumerable, ale this má pro jednoduchost List)
        /// </summary>
        IEnumerable<IDataMatrixCell> IDataMatrixRow.Cells { get { return this.Cells; } }
    }
    /// <summary>
    /// Defaultní implementace IDataMatrixCell
    /// </summary>
    internal class DataMatrixCell : IDataMatrixCell
    {
        /// <summary>
        /// ID sloupce, klíčový název.
        /// Buňky řádků, které budou mít shodný ColumnKey, budou vypsány pod sebou.
        /// Pokud jeden řádek bude mít více buněk se shodným ColumnKey, dojde k chybě.
        /// ColumnKey nesmí být null ani empty, dojde k chybě.
        /// </summary>
        public string ColumnKey { get; set; }
        /// <summary>
        /// Titulek sloupce.
        /// Pokud bude pro jeden ColumnKey více různých ColumnCaption, pak se zobrazí nejdelší z nich.
        /// </summary>
        public string ColumnCaption { get; set; }
        /// <summary>
        /// Pořadí sloupce.
        /// Pokud budou dva sloupce se shodným pořadím, budou tříděny podle ColumnKey.
        /// Pokud bude pro jeden ColumnKey více různých hodnot ColumnOrder, pak se použije ta nejnižší z nich, větší než 0.
        /// Pokud na vstupu bude pro nový sloupec hodnota 0, pak se takový sloupec zařadí jako poslední.
        /// </summary>
        public int ColumnOrder { get; set; }
        /// <summary>
        /// Obsah buňky. Může být null.
        /// </summary>
        public object CellValue { get; set; }
    }
    #endregion
    #region Interface IDataMatrixRow a IDataMatrixCell
    /// <summary>
    /// Předpis pro data jednoho vstupujícího řádku s daty.
    /// </summary>
    public interface IDataMatrixRow
    {
        /// <summary>
        /// Řádek obsahuje sadu buněk, každá buňka obsahuje popis sloupce a hodnotu řádku.
        /// </summary>
        IEnumerable<IDataMatrixCell> Cells { get; }
    }
    /// <summary>
    /// Předpis pro data
    /// </summary>
    public interface IDataMatrixCell
    {
        /// <summary>
        /// ID sloupce, klíčový název.
        /// Buňky řádků, které budou mít shodný ColumnKey, budou vypsány pod sebou.
        /// Pokud jeden řádek bude mít více buněk se shodným ColumnKey, dojde k chybě.
        /// ColumnKey nesmí být null ani empty, dojde k chybě.
        /// </summary>
        string ColumnKey { get; }
        /// <summary>
        /// Titulek sloupce.
        /// Pokud bude pro jeden ColumnKey více různých ColumnCaption, pak se zobrazí nejdelší z nich.
        /// </summary>
        string ColumnCaption { get; }
        /// <summary>
        /// Pořadí sloupce.
        /// Pokud budou dva sloupce se shodným pořadím, budou tříděny podle ColumnKey.
        /// Pokud bude pro jeden ColumnKey více různých hodnot ColumnOrder, pak se použije ta nejnižší z nich, větší než 0.
        /// Pokud na vstupu bude pro nový sloupec hodnota 0, pak se takový sloupec zařadí jako poslední.
        /// </summary>
        int ColumnOrder { get; }
        /// <summary>
        /// Obsah buňky. Může být null.
        /// </summary>
        object CellValue { get; }
    }
    #endregion
}
